package com.gearbox.core.task;

import com.gearbox.core.configuration.AsConfiguration;
import com.gearbox.core.configuration.SystemConfiguration;
import com.gearbox.core.constant.NodeStatusEnum;
import com.gearbox.core.driver.AsHandler;
import com.gearbox.core.model.Node;
import com.gearbox.core.service.NodeService;
import com.huaweicloud.sdk.as.v1.model.ScalingGroupInstance;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class ScalingGroupInstanceDeleteTask {
    private static final Logger LOG = LoggerFactory.getLogger(ScalingGroupInstanceDeleteTask.class);

    @Autowired
    private NodeService nodeService;

    @Autowired
    private AsConfiguration asConfiguration;

    @Autowired
    private SystemConfiguration systemConfiguration;

    @Autowired
    private AsHandler asHandler;

    @Scheduled(initialDelay = 10L, fixedDelayString = "${task.delete-instance-period}", timeUnit = TimeUnit.SECONDS)
    private void deleteDrainInstance() {
        try {
            LOG.info("do DeleteGroupInstanceTask begin");
            List<Node> drainNodes = nodeService.listUnstableDrainNodes();
            LOG.info("list all drain slurm nodes, nodes={}", drainNodes);
            List<Node> nodesToDelete = getNodesToDelete(drainNodes);
            LOG.info("get all nodes to be delete, nodes={}", nodesToDelete);
            delete(nodesToDelete);
            LOG.info("do DeleteGroupInstanceTask end");
        } catch (Throwable e) {
            LOG.error("do task error", e);
        }
    }

    @Scheduled(initialDelay = 10L, fixedDelayString = "${task.diff-instance-and-node-period}",
        timeUnit = TimeUnit.SECONDS)
    private void diffGroupInstanceAndNodeTask() {
        List<ScalingGroupInstance> groupInstances = asHandler.listAllScalingInstance();
        Set<String> nodeNames = nodeService.listUnstableNodes().stream().map(Node::getName).collect(Collectors.toSet());
        groupInstances.stream()
            .filter(this::isInService)
            .filter(instance -> !isGroupInstanceInCluster(instance, nodeNames))
            .forEach(this::recordLogAndDeleteInstanceIfNecessary);
    }

    void recordLogAndDeleteInstanceIfNecessary(ScalingGroupInstance instance) {
        if (instance.getCreateTime()
            .isBefore(OffsetDateTime.now().minusMinutes(systemConfiguration.getRegisterTimeoutMinutes()))) {
            LOG.warn("instance:[{}] not in cluster", instance.getInstanceName());
            deleteInstance(instance);
        }
    }

    void deleteInstance(ScalingGroupInstance instance) {
        try {
            asHandler.deleteInstances(Collections.singletonList(instance.getInstanceId()));
            nodeService.remove(
                new Node(instance.getInstanceName().toLowerCase(Locale.ROOT), NodeStatusEnum.WORKING));
        } catch (Exception e) {
            LOG.error("delete instance[{}] error", instance.getInstanceName(), e);
        }
    }

    boolean isGroupInstanceInCluster(ScalingGroupInstance instance, Set<String> nodeNames) {
        return nodeNames.contains(instance.getInstanceName().toLowerCase(Locale.ROOT));
    }

    boolean isInService(ScalingGroupInstance instance) {
        return instance.getLifeCycleState().equals(ScalingGroupInstance.LifeCycleStateEnum.INSERVICE);
    }

    List<Node> getNodesToDelete(List<Node> nodes) {
        return nodes.parallelStream()
            .filter(this::isNeedDelete)
            .limit(asConfiguration.getDeleteInstanceLimit())
            .collect(Collectors.toList());
    }

    boolean isNeedDelete(Node node) {
        try {
            return !nodeService.isWorking(node.getName());
        } catch (Exception e) {
            return false;
        }
    }

    void delete(List<Node> nodesToDelete) {
        if (nodesToDelete.isEmpty()) {
            return;
        }
        Map<String, ScalingGroupInstance> groupInstanceMap = asHandler.getNameMapGroupInstances();
        List<String> instancesToDelete = nodesToDelete.stream()
            .map(Node::getName)
            .map(groupInstanceMap::get)
            .filter(Objects::nonNull)
            .map(ScalingGroupInstance::getInstanceId)
            .collect(Collectors.toList());
        if (instancesToDelete.isEmpty()) {
            LOG.info("no instance to delete");
            return;
        }
        asHandler.deleteInstances(instancesToDelete);
    }
}
